﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Nature.Toolkit
{
    public class CrossJoin<T, TCollection> : IEnumerable<T[]> 
        where TCollection : class, IEnumerable<T>
    {
        #region Private Fields
        TCollection[] m_collections;
        IEnumerable<T> m_upperCollection;
        IEnumerable<T[]> m_lowerSegment;
        #endregion

        #region Nested Types
        class SingleElementArraysCollection : IEnumerable<T[]>
        {
            IEnumerable<T> m_collection;
            public SingleElementArraysCollection(IEnumerable<T> collection) { m_collection = collection; }

            #region IEnumerable<T[]> Members

            IEnumerator<T[]> IEnumerable<T[]>.GetEnumerator()
            {
                foreach (var t in m_collection) { yield return new T[] { t }; }
            }

            #endregion

            #region IEnumerable Members

            IEnumerator IEnumerable.GetEnumerator()
            {
                foreach (var t in m_collection) { yield return new T[] { t }; }
            }

            #endregion
        } 
        #endregion       

        public CrossJoin(IEnumerable<TCollection> collections) 
        {
            m_collections = collections.ToArray();
            m_upperCollection = m_collections.FirstOrDefault() ?? Enumerable.Empty<T>();            
            if (m_collections.Length > 2)
            {
                m_lowerSegment = new CrossJoin<T, TCollection>(collections.Skip(1));
            }
            else if (m_collections.Length == 2)
            {
                m_lowerSegment = new SingleElementArraysCollection(m_collections.Last());
            }
            else
            {
                m_lowerSegment = new T[0][];
            }
        }

        public TCollection[] GetBaseCollections()
        {
            return m_collections.ToArray();
        }

        public TCollection this[int index]
        {
            get { return m_collections[index]; }
        }

        public IEnumerable<TCollection> BaseCollections
        {
            get { return m_collections; }
        }

        public int IndexOf(TCollection collection)
        {
            return Array.IndexOf(m_collections, collection);
        }

        public int Length
        {
            get { return m_collections.Length; }
        }

        public int Count()
        {
            int count = 1;
            foreach( var collection in m_collections) { count *= collection.Count(); }
            return count;
        }

        #region IEnumerable<T[]> Members

        public IEnumerator<T[]> GetEnumerator()
        {
            foreach (T upperValue in m_upperCollection)
            {
                T[] array = new T[m_collections.Length];
                array[0] = upperValue;
                foreach (T[] lowerArray in m_lowerSegment)
                {
                    for (int i = 1; i < array.Length; ++i)
                    {
                        array[i] = lowerArray[i - 1];
                    }
                    yield return array;
                }
                if (array.Length == 1) { yield return array; }
                
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            foreach (T upperValue in m_upperCollection)
            {
                foreach (T[] lowerArray in m_lowerSegment)
                {
                    T[] array = new T[m_collections.Length];
                    array[0] = upperValue;
                    for (int i = 0; i < lowerArray.Length; ++i)
                    {
                        array[i + 1] = lowerArray[i];
                    }
                    yield return array;
                }
            }
        }

        #endregion
    }

    public class CrossJoin<T> : CrossJoin<T, IEnumerable<T>>
    {
        public CrossJoin(IEnumerable<IEnumerable<T>> collections) : base(collections) { }
    }

    public class CrossJoin : CrossJoin<object>
    {
        public CrossJoin(IEnumerable<IEnumerable<object>> collections) : base(collections) { }
    }
}
